﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using MouseExtender.Logic.Common;
using MouseExtender.Logic.Entities;
using MouseExtender.Logic.Enums;
using MouseExtender.Logic.Managers;
using Color = System.Windows.Media.Color;
using DrawingBitmap = System.Drawing.Bitmap;
using DrawingGraphics = System.Drawing.Graphics;
using DrawingIcon = System.Drawing.Icon;

namespace MouseExtender.Logic.Helpers
{
    public class IconHelper
    {
        private static readonly MethodInfo _extractIconWithIndex = typeof(System.Drawing.Icon).GetMethod("ExtractAssociatedIcon", BindingFlags.Static | BindingFlags.NonPublic);

        private static BitmapSource GetBitmapSource(string fileName, IconSize iconSize)
        {
            IntPtr hImgSmall = IntPtr.Zero;
            uint iconSizeFlag = iconSize == IconSize.Icon16x16 ? Win32Helper.SHGFI_ICON | Win32Helper.SHGFI_SMALLICON : Win32Helper.SHGFI_ICON | Win32Helper.SHGFI_LARGEICON;
            /*
             * 
             * | Win32.SHGFI_USEFILEATTRIBUTES;
            uint attributes = Win32.FILE_ATTRIBUTE_NORMAL;
            FileAttributes pathAttributes = File.GetAttributes(fileName);
            if ((pathAttributes & FileAttributes.Directory) == FileAttributes.Directory)
            {
                    attributes = Win32.FILE_ATTRIBUTE_DIRECTORY;
            }
            */
            Win32Helper.SHFILEINFO shinfo = new Win32Helper.SHFILEINFO();

            hImgSmall = Win32Helper.SHGetFileInfo(fileName, 0/* attributes */, ref shinfo, (uint)Marshal.SizeOf(shinfo), iconSizeFlag);
            BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHIcon(shinfo.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            Win32Helper.DestroyIcon(shinfo.hIcon);
            return bitmapSource;
        }

        public static BitmapSource BitmapToBitmapSource(System.Drawing.Bitmap bitmap)
        {
            return GetBitmapSourceFromHandle(bitmap.GetHbitmap(), true);
        }

        public static BitmapSource IconToBitmapSource(System.Drawing.Icon icon)
        {
            return GetBitmapSourceFromHandle(icon.Handle, false);
        }

        private static BitmapSource GetBitmapSourceFromHandle(IntPtr objHandle, bool freeHandle)
        {
            BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(objHandle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            if (freeHandle)
                Win32Helper.DeleteObject(objHandle);
            return bitmapSource;
        }

        public static System.Drawing.Bitmap BitmapSourceToBitmap(BitmapSource bitmapSource)
        {
            int width = bitmapSource.PixelWidth;
            int height = bitmapSource.PixelHeight;
            int stride = width * ((bitmapSource.Format.BitsPerPixel + 7) / 8);
            byte[] bits = new byte[height * stride];
            bitmapSource.CopyPixels(bits, stride, 0);
            unsafe
            {
                fixed (byte* pBits = bits)
                {
                    IntPtr ptr = new IntPtr(pBits);
                    System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format32bppPArgb, ptr);
                    return bitmap;
                }
            }
        }

        [Obsolete]
        private static BitmapSource MakeGrayscale(BitmapSource original)
        {
            System.Drawing.Bitmap originalBitmap = BitmapSourceToBitmap(original);
            System.Drawing.Bitmap newBitmap = new System.Drawing.Bitmap(originalBitmap.Width, originalBitmap.Height);

            System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(newBitmap);
            System.Drawing.Imaging.ColorMatrix colorMatrix = new System.Drawing.Imaging.ColorMatrix(
                    new float[][]
                {
                new float[] {.50f, .50f, .50f, 0, 0},
                new float[] {.59f, .59f, .59f, .0f, .0f},
                new float[] {.11f, .11f, .11f, 0, 0},
                new float[] {0, 0, 0, 1, 0},
                new float[] {0, 0, 0, 0, 1}
                }
            );

            System.Drawing.Imaging.ImageAttributes attributes = new System.Drawing.Imaging.ImageAttributes();
            attributes.SetColorMatrix(colorMatrix);

            graphics.DrawImage(originalBitmap, new System.Drawing.Rectangle(0, 0, originalBitmap.Width, originalBitmap.Height),
                    0, 0, originalBitmap.Width, originalBitmap.Height, System.Drawing.GraphicsUnit.Pixel, attributes);
            graphics.Dispose();

            BitmapSource newBitmapSource = BitmapToBitmapSource(newBitmap);
            return newBitmapSource;
        }

        public static Color GetIconMatchedColor(BitmapSource source, int checkedColorPoints)
        {
            if (checkedColorPoints < 0)
                throw new ArgumentException("'checkedColorPoints' must be positive.");

            System.Drawing.Bitmap image = BitmapSourceToBitmap(source);
            int checkedPointsInRow = (int)Math.Sqrt(checkedColorPoints);
            Dictionary<int, List<float>> hueScale = new Dictionary<int, List<float>>();

            int xOffset = (int)(image.Width / (checkedPointsInRow + 1));
            int yOffset = (int)(image.Height / (checkedPointsInRow + 1));
            for (int i = 0; i < checkedPointsInRow; i++)
            {
                for (int j = 0; j < checkedPointsInRow; j++)
                {
                    System.Drawing.Color color = image.GetPixel((i + 1) * xOffset, (j + 1) * yOffset);
                    float currentHue = color.GetHue();
                    if (currentHue != 0)
                    {
                        int range = (int)Math.Floor((double)(currentHue / 40));
                        if (!hueScale.ContainsKey(range))
                            hueScale[range] = new List<float>();
                        hueScale[range].Add(currentHue);
                    }
                }
            }
            float hue = 0;
            float saturation = 0;
            float brightness = 0;
            Color newColor = Colors.Black;

            if (hueScale.Count > 0)
            {
                int majorRange = 0;
                int majorRangeHueCount = 0;
                foreach (KeyValuePair<int, List<float>> hueKeyValuePair in hueScale)
                {
                    if (hueKeyValuePair.Value.Count > majorRangeHueCount)
                    {
                        majorRangeHueCount = hueKeyValuePair.Value.Count;
                        majorRange = hueKeyValuePair.Key;
                    }
                }


                foreach (float h in hueScale[majorRange])
                {
                    hue += h;
                }
                hue = hue / hueScale[majorRange].Count;
                Random random = new Random(DateTime.Now.Millisecond);
                saturation = random.Next(100, 150);
                brightness = random.Next(70, 85);

                System.Drawing.Color fromHsbColor = new HSBColor(hue, saturation, brightness).Color;
                newColor = new Color()
                {
                    A = fromHsbColor.A,
                    R = fromHsbColor.R,
                    G = fromHsbColor.G,
                    B = fromHsbColor.B
                };
            }

            return newColor;
        }

        public static BitmapSource GetItemIcon(LauncherItem item)
        {
            BitmapSource source = null;
            if (!item.IsUrl)
            {
                string iconFullPath = PathHelper.GetFullPath(item.IconSource);
                string iconSourceExtension = Path.GetExtension(iconFullPath).ToLower();
                if ((Constants.ImageExtensions.Contains(iconSourceExtension)) && (item.IconSource != item.LaunchPath))
                {
                    using (System.Drawing.Bitmap icon = new System.Drawing.Bitmap(iconFullPath))
                    {
                        source = BitmapToBitmapSource(icon);
                    }
                }
                else
                {
                    if (iconFullPath.IsDirectory())
                    {
                        source = GetBitmapSource(iconFullPath, SettingsManager.CurrentSettings.IconSize);
                    }
                    else
                    {
                        source = GetIconAsBitmapSource(iconFullPath, item.IconIndex);
                    }
                }
            }
            else
            {
                //if thumbs assigned then load
                if (item.ThumbsImageId != null)
                {
                    try
                    {
                        ExternalImage imageData = ExternalResourcesManager.Thumbs[item.ThumbsImageId];
                        if (imageData != null)
                        {
                            DrawingIcon favIco = null;
                            DrawingBitmap favIcoAsBitmap = null;

                            if (!imageData.IsImage)
                            {
                                favIco = new DrawingIcon(new MemoryStream(imageData.ImageData));
                            }
                            else
                            {
                                favIcoAsBitmap = (DrawingBitmap)DrawingBitmap.FromStream(new MemoryStream(imageData.ImageData));
                            }
                            //if compact view then just use original favico
                            if (SettingsManager.CurrentSettings.IconSize == IconSize.Icon16x16)
                            {
                                if (!imageData.IsImage)
                                {
                                    //System.Runtime.InteropServices.COMException (0x88980003): Exception from HRESULT: 0x88980003 at System.Windows.Interop.InteropBitmap.InitializeFromHBitmap
                                    //source = IconToBitmapSource(favIco);
                                    //TODO: Remove this. Stub. won't work for all icons
                                    DrawingBitmap favIcoStub = (DrawingBitmap)DrawingBitmap.FromStream(new MemoryStream(imageData.ImageData));
                                    source = BitmapToBitmapSource(favIcoStub);
                                }
                                else
                                {
                                    source = BitmapToBitmapSource(favIcoAsBitmap);
                                }
                            }
                            else
                            {
                                DrawingBitmap back = MouseExtender.Properties.Resources.file_type_url_32x32;
                                using (DrawingGraphics transform = DrawingGraphics.FromImage(back))
                                {
                                    if (!imageData.IsImage)
                                    {
                                        transform.DrawIcon(favIco, new System.Drawing.Rectangle(16, 16, 16, 16));
                                    }
                                    else
                                    {
                                        transform.DrawImage(favIcoAsBitmap, new System.Drawing.Rectangle(16, 16, 16, 16));
                                    }
                                    transform.Save();
                                }

                                source = BitmapToBitmapSource(back);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        //Thumbs data is corrupted. Non critical. Just load standart ico
                        Logger.HandleNonCriticalError(ex);
                    }
                }

                //if image couldn't parsed or file is corrupted then load standart icon
                if (source == null)
                {
                    if (SettingsManager.CurrentSettings.IconSize == IconSize.Icon16x16)
                        source = new BitmapImage(new Uri(Constants.ResourcesUri.FileTypeUrl16x16, UriKind.Relative));
                    else
                        source = new BitmapImage(new Uri(Constants.ResourcesUri.FileTypeUrl32x32, UriKind.Relative));
                }
            }
            return source;
        }

        /// <summary>
        /// Get Icon as BitmapSource for files
        /// </summary>
        /// <param name="filename">filename</param>
        /// <param name="iconIndex">icon index</param>
        /// <returns></returns>
        private static BitmapSource GetIconAsBitmapSource(string filename, int iconIndex)
        {
            if (File.Exists(filename))
            {
                if (filename.StartsWith(@"\\"))
                { //network file
                    Win32Helper.SHFILEINFO shinfo = new Win32Helper.SHFILEINFO();
                    IntPtr hIcon = Win32Helper.SHGetFileInfo(filename, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), Win32Helper.SHGFI_ICON | Win32Helper.SHGFI_LARGEICON);
                    using (DrawingIcon drIcon = DrawingIcon.FromHandle(shinfo.hIcon))
                    {
                        using (System.Drawing.Bitmap bitmap = drIcon.ToBitmap())
                        {
                            return BitmapToBitmapSource(bitmap);
                        }
                    }
                }
                else
                {
                    object result = _extractIconWithIndex.Invoke(null, new object[] { filename, iconIndex });
                    using (System.Drawing.Icon icon = result as System.Drawing.Icon)
                    {
                        using (System.Drawing.Bitmap bitmap = icon.ToBitmap())
                        {
                            return BitmapToBitmapSource(bitmap);
                        }
                    }
                }
            }
            else
            {
                //Set broken icon
                string iconUri = SettingsManager.CurrentSettings.IconSize == IconSize.Icon16x16 ? Constants.ResourcesUri.ItemBroken16x16 : Constants.ResourcesUri.ItemBroken32x32;
                return new BitmapImage(new Uri(iconUri, UriKind.Relative));
            }
        }

        public static void LoadIcons(System.Windows.Controls.ListBox iconsListBox, LauncherItem item)
        {
            iconsListBox.Items.Clear();
            string iconSourceFullPath = PathHelper.GetFullPath(item.IconSource);
            string iconSourceExtension = System.IO.Path.GetExtension(iconSourceFullPath).ToLower();
            if ((Constants.ImageExtensions.Contains(iconSourceExtension)) && (item.IconSource != item.LaunchPath))
            {
                System.Windows.Controls.Image iconImage = new System.Windows.Controls.Image();
                iconImage.Width = iconImage.Height = 32d;
                iconImage.Margin = new Thickness(5d);
                using (System.Drawing.Bitmap icon = new System.Drawing.Bitmap(iconSourceFullPath))
                {
                    iconImage.Source = BitmapToBitmapSource(icon);
                }
                iconsListBox.Items.Add(iconImage);
                return;
            }
            int refSmall = 0, refBig = 0;
            int maxIcons = Win32Helper.ExtractIconExA(iconSourceFullPath, -1, ref refSmall, ref refBig, 1);
            for (int i = 0; i < maxIcons; i++)
            {
                try
                {
                    BitmapSource source = GetIconAsBitmapSource(iconSourceFullPath, i);
                    if (source != null)
                    {
                        System.Windows.Controls.Image iconImage = new System.Windows.Controls.Image();
                        iconImage.Width = iconImage.Height = 32d;
                        iconImage.Margin = new Thickness(5d);
                        iconImage.Source = source;
                        iconsListBox.Items.Add(iconImage);
                    }
                }
                catch (Exception ex)
                {
                    Logger.HandleNonCriticalError(ex);
                }
            }
            iconsListBox.SelectedIndex = item.IconIndex;
        }

        public static void ExtractFavoriteIcon(object launcherItemState)
        {
            LauncherItem launcherItem = (LauncherItem)launcherItemState;

            Debug.WriteLine(string.Format("Starting favico downloading. Path = {0}. Thread = {1}", launcherItem.LaunchPath, Thread.CurrentThread.ManagedThreadId));

            bool updateGUI = false;

            try
            {
                Uri uri = new Uri(launcherItem.LaunchPath);
                WebClient client = new WebClient();
                client.UseDefaultCredentials = true;
                byte[] favico = null;
                bool iconDownloaded = false;
                bool isImage = false;
                //try to download page, parse it and find favico location
                try
                {
                    //parseit
                    //<link rel="icon" href="http://s.ytimg.com/yt/favicon-vfl122048.ico" type="image/x-icon">
                    //<link id="ctl00_favlink" rel="shortcut icon" href="pic/favicon.ico" type="image/x-icon" />
                    //1.href and rel can be from differenet sites of each other
                    //2.href can be relative as well as absolute

                    string pageContent = client.DownloadString(uri);

                    string titlePattern = @"<title>(?<title>.*?)</title>";
                    string titleValue = Regex.Match(pageContent, titlePattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline).Groups["title"].Value;
                    if (!string.IsNullOrEmpty(titleValue) && string.IsNullOrEmpty(launcherItem.ToolTip))
                    {
                        string titleNormalized = titleValue.Replace("\r", "").Replace("\t", " ").Replace("\n", " ");
                        launcherItem.ToolTip = System.Web.HttpUtility.HtmlDecode(titleNormalized);
                    }

                    //<link[^>]*?rel\s*=\s*"(?:shortcut )?icon"[^>]*?href\s*=\s*"(?<uri>[^>]*?)"[^<]*?/?>
                    //<link[^>]*?href\s*=\s*"(?<uri>[^>]*?)"[^>]*?rel\s*=\s*"(?:shortcut )?icon"[^<]*?/?>
                    string favPattern = @"<link[^>]*?rel\s*=\s*""(?:shortcut )?icon""[^>]*?href\s*=\s*""(?<uri>[^>]*?)""[^<]*?/?>|<link[^>]*?href\s*=\s*""(?<uri>[^>]*?)""[^>]*?rel\s*=\s*""(?:shortcut )?icon""[^<]*?/?>";
                    Match firstMatch = Regex.Match(pageContent, favPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                    if (firstMatch != null && firstMatch.Success)
                    {
                        string favIcoPath = firstMatch.Groups["uri"].Value;
                        if (!string.IsNullOrEmpty(favIcoPath))
                        {
                            Uri extractedUri = new Uri(favIcoPath, UriKind.RelativeOrAbsolute);
                            if (!extractedUri.IsAbsoluteUri)
                            {
                                extractedUri = new Uri(uri, extractedUri);
                            }
                            Debug.WriteLine(string.Format("Favico url found = [{0}]. Final url = [{1}]", favIcoPath, extractedUri.AbsoluteUri));
                            DownloadFavicon(client, extractedUri, ref favico, ref iconDownloaded, ref isImage);
                        }
                    }
                }
                //webexception + argument exception
                catch (Exception ex)
                {
                    var internalError = new ApplicationException(string.Format("Favico page download/parse/convert to bitmap error: [{0}]", launcherItem.LaunchPath), ex);
                    Logger.HandleNonCriticalError(internalError);
                }

                //if not found then search at default location
                if (!iconDownloaded)
                {
                    string hostFavico = string.Format("{0}://{1}/favicon.ico", uri.Scheme, uri.Host);
                    Debug.WriteLine(string.Format("Try to download host favico = [{0}]", hostFavico));
                    DownloadFavicon(client, new Uri(hostFavico), ref favico, ref iconDownloaded, ref isImage);
                }

                //if any icon found then save it and update GUI
                if (iconDownloaded)
                {
                    string id = Guid.NewGuid().ToString();
                    launcherItem.ThumbsImageId = id;
                    ExternalResourcesManager.Thumbs[id] = new ExternalImage() { Id = id, ImageData = favico, IsImage = isImage };
                    ExternalResourcesManager.SaveThumbs();

                    Debug.WriteLine(string.Format("Favico downloaded successfully. Length = {0}", favico.Length));
                    updateGUI = true;
                }
            }
            //catch only expected error like webexception, formatexception
            catch (Exception ex)
            {
                var internalError = new ApplicationException(string.Format("Error occured during exatracting FAVICON for the item: [{0}]", launcherItem.LaunchPath), ex);
                Logger.HandleNonCriticalError(internalError);
            }

            if (String.IsNullOrEmpty(launcherItem.ToolTip))
            {
                launcherItem.ToolTip = launcherItem.LaunchPath;
                updateGUI = true;
            }

            if (updateGUI)
            {

                App.UIContext.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                         {
                             SettingsManager.CurrentSettings.SettingChanged();
                             App.MainWindowInstance.ucLauncher.Refresh();
                         }));
            }
        }

        private static void DownloadFavicon(WebClient client, Uri extractedUri, ref byte[] favico, ref bool iconDownloaded, ref bool isImage)
        {
            favico = client.DownloadData(extractedUri);
            //check if ico
            if (extractedUri.AbsoluteUri.EndsWith(".ico", StringComparison.InvariantCultureIgnoreCase))
            {
                new DrawingIcon(new MemoryStream(favico));
            }
            //check if image
            else
            {
                DrawingBitmap.FromStream(new MemoryStream(favico));
                isImage = true;
            }
            iconDownloaded = true;
        }
    }
}
